use crate::errors::{ParserErrorType};
use crate::types::{self,SourceToken,Token,Type,Constraint,Wrapped};
use crate::traversals::{everything_on_types};
use purescript_ast::{names};

pub type QualifiedOpName = types::QualifiedName<names::OpName>;

pub type QualifiedProperName = types::QualifiedName<names::ProperName>;

pub fn to_qual_op_name(errors:&mut Vec<ParserErrorType>,tok:SourceToken) -> QualifiedOpName  {
    let (qname,errs) = to_qualified_name(|str| names::OpName::AnyOpName(String::from(str)),&tok);
    if errs.len() > 0 {
        errors.extend(errs);
    }
    qname
}

pub fn to_qual_proper_name(errors:&mut Vec<ParserErrorType>,tok:SourceToken) -> QualifiedProperName  {
    let (qname,errs) = to_qualified_name(|str| names::ProperName::ConstructorName(String::from(str)),&tok);
    if errs.len() > 0 {
        errors.extend(errs);
    }
    qname
}

pub fn to_qualified_name<A:Clone>(k:fn(&str) -> A,tok:&SourceToken) -> (types::QualifiedName<A>,Vec<ParserErrorType>) {
    match &tok.value {
        Token::TokLowerName(a) | Token::TokUpperName(a) | Token::TokSymbolName(a) | Token::TokOperator(a) => 
            (types::QualifiedName {tok:tok.clone(),name:k(a.as_str()),module:None },vec![]),
        Token::TokQualLowerName(qual,a) | Token::TokQualUpperName(qual,a) | Token::TokQualSymbolName(qual,a)
        | Token::TokQualOperator(qual,a) => {
            let (mname,err) = to_module_name(tok,&qual);
            (types::QualifiedName {tok:tok.clone(),name:k(a.as_str()),module:mname},err)
        },
        Token::TokSymbolDoubleDot => {
            (types::QualifiedName {tok:tok.clone(),name:k(".."),module:None },vec![])
        },
        Token::TokOperatorHash => (types::QualifiedName {tok:tok.clone(),name:k("#"),module:None },vec![]),
        Token::TokOperatorAt => (types::QualifiedName {tok:tok.clone(),name:k("@"),module:None },vec![]),
        Token::TokOperatorSub => (types::QualifiedName {tok:tok.clone(),name:k("-"),module:None },vec![]),
        _ => panic!("Invalid qualified name: {}",tok.value)
    }
}

pub fn to_module_name(_:&SourceToken,ns:&Vec<String>) -> (Option<names::ModuleName>,Vec<ParserErrorType>) {
    if ns.len() == 0 {
        return (None,vec![])
    }
    let mut ret_err = vec![];
    for n in ns {
        if !is_valid_module_namespace(n) {
            ret_err.push(ParserErrorType::ErrModuleName);
            break;
        }
    }
    let mut join_module_name = String::default();
    let leni32 = ns.len() as i32;
    for idx in 0..leni32 as i32 {
      join_module_name.push_str(ns[idx as usize].as_str());
      if idx < leni32 - 2 {
          join_module_name.push('.');
      }
    }
    (Some(names::ModuleName(join_module_name)),ret_err)
}


fn is_valid_module_namespace(text:&str) -> bool {
    for chr in text.chars() {
        if chr == '_' && chr == '\'' {
            return false
        }
    }
    true
}

pub fn to_label(tok:&SourceToken) -> types::Label {
    let by_str = |str| {
        types::Label {tok:tok.clone(),name:String::from(str)}
    };
    match &tok.value {
        Token::TokLowerName(a) | Token::TokString(_,a) | Token::TokRawString(a) => types::Label {tok:tok.clone(),name:a.clone() },
        Token::TokForall => by_str("forall"),
        Token::TokAdo =>  by_str("ado"),
        Token::TokAs =>  by_str("as"),
        Token::TokCase =>  by_str("case"),
        Token::TokClass =>  by_str("class"),
        Token::TokData =>  by_str("data"),
        Token::TokDerive =>  by_str("derive"),
        Token::TokDo =>  by_str("do"),
        Token::TokElse => by_str("else"),
        Token::TokFalse => by_str("false"),
        Token::TokForeign => by_str("foreign"),
        Token::TokHiding => by_str("hiding"),
        Token::TokImport => by_str("import"),
        Token::TokIf => by_str("if"),
        Token::TokIn => by_str("in"),
        Token::TokInfix => by_str("infix"),
        Token::TokInfixl => by_str("infixl"),
        Token::TokInfixr => by_str("infixr"),
        Token::TokInstance => by_str("instance"),
        Token::TokKind => by_str("kind"),
        Token::TokLet => by_str("let"),
        Token::TokModule => by_str("module"),
        Token::TokNewtype => by_str("newtype"),
        Token::TokNominal => by_str("nominal"),
        Token::TokOf => by_str("of"),
        Token::TokPhantom => by_str("phantom"),
        Token::TokRepresentational => by_str("representational"),
        Token::TokRole => by_str("role"),
        Token::TokThen => by_str("then"),
        Token::TokTrue => by_str("true"),
        Token::TokType => by_str("type"),
        Token::TokWhere => by_str("where"),
        _ => panic!("Invalid label: {}",tok.value)
    }
}


pub fn to_name<A>(k:impl Fn(String) -> A,token:SourceToken) -> Result<types::Name<A>,ParserErrorType> {
    let clone_token = token.clone();
    match token.value {
       Token::TokLowerName(a) | Token::TokUpperName(a) | Token::TokSymbolName(a) | Token::TokOperator(a) | Token::TokHole(a) => {
           let nval = types::Name {
               tok:clone_token,
               value: k(a)
           };
           Ok(nval)
       },
       Token::TokAs => to_name_by_str(k,token,"as"),
       Token::TokHiding => to_name_by_str(k,token,"hiding"),
       Token::TokKind => to_name_by_str(k,token,"kind"),
       Token::TokRole => to_name_by_str(k,token,"role"),
       Token::TokNominal => to_name_by_str(k,token,"nominal"),
       Token::TokRepresentational => to_name_by_str(k,token,"representational"),
       Token::TokPhantom => to_name_by_str(k,token,"phantom"),
       Token::TokString(_,_) => Err(ParserErrorType::ErrQuotedPun),
       Token::TokRawString(_) => Err(ParserErrorType::ErrQuotedPun),
        _ => panic!("Internal parser error: {}",token.value)
    }
}

fn to_name_by_str<A>(k:impl Fn(String) -> A,tok:SourceToken,str:&str) -> Result<types::Name<A>,ParserErrorType> {
    Ok(types::Name {
        tok:tok.clone(),
        value: k(String::from( str))
    })
}

pub fn to_string(token:SourceToken) -> (SourceToken,String)  {
    match &token.value {
        Token::TokString(_,a) | Token::TokRawString(a) => (token.clone(),a.clone()),
        _ =>  panic!("Invalid string literal: {}",token.value)
    }
}

pub fn to_char(token:SourceToken) -> (SourceToken,char) {
    match token.value {
        Token::TokChar(_,a) => (token.clone(),a),
        _ => panic!("Invalid char literal: {}",token.value)
    }
}

pub fn to_number(token:SourceToken) -> (SourceToken,Result<i32,f64>) {
    match &token.value {
        Token::TokInt(_,num) => (token.clone(),Ok(*num)),
        Token::TokNumber(_,num) => (token.clone(),Err(*num)),
        _ => panic!("Invalid number literal: {}",token.value)
    }
}

pub fn to_int(token:SourceToken) -> (SourceToken,i32) {
    match &token.value {
        Token::TokInt(_,num) => (token.clone(),*num),
        _ => panic!("Invalid integer literal: {}",token.value)
    }
}

pub fn to_boolean(token:SourceToken) -> (SourceToken,bool) {
    match &token.value {
        Token::TokTrue => (token.clone(),true),
        Token::TokFalse => (token.clone(),false),
        _ => panic!("Invalid boolean literal: {}",token.value)
    }
}

pub fn check_no_foralls<A:Clone>(ty:&Type<A>) -> Vec<ParserErrorType> {
    everything_on_types(join_err,no_foralls_k, ty)
}

fn join_err(mut a:Vec<ParserErrorType>,b:Vec<ParserErrorType>) -> Vec<ParserErrorType> {
    a.extend(b);
    a
}

fn no_foralls_k<A:Clone>(ty:&Type<A>) -> Vec<ParserErrorType> {
    match ty {
        Type::TypeForall(_,_,_,_,_) => vec![ParserErrorType::ErrToken],
        _ => vec![]
    }
}



pub fn check_no_wildcards<A:Clone>(ty:&Type<A>) -> Vec<ParserErrorType> {
  everything_on_types(|mut a,b| {a.extend(b); a},no_wildcards_k,ty)
}


fn no_wildcards_k<A:Clone>(ty:&Type<A>) -> Vec<ParserErrorType> {
    match ty {
        Type::TypeWildcard(_,_) => vec![ParserErrorType::ErrWildcardInType],
        Type::TypeHole(_,_) => vec![ParserErrorType::ErrHoleInType],
        _ => vec![]
    }
}

pub fn to_constraint<A>(ty:&Type<A>) -> (Constraint<A>,Vec<ParserErrorType>) where A : Default + Clone {
    match ty {
        Type::TypeParens(a,wrap) => {
            let mut inner_ty:&Type<A> = &wrap.value;
            loop {
                if let Type::TypeParens(_,wrap) = inner_ty {
                    inner_ty = &wrap.value;
                } else {
                    break;
                }
            }
            let (c,err) = to_constraint_convert(Default::default(),vec![],inner_ty);
            let new_wrap = Box::new(Wrapped {open:wrap.open.clone(),close:wrap.close.clone(),value:c });
            (Constraint::ConstraintParens(a.clone(),new_wrap),err)
        },
        t => to_constraint_convert(Default::default(),vec![],t)
    }
}

fn to_constraint_convert<A:Clone>(_ann:A,mut acc:Vec<Box<Type<A>>>,mut ty:&Type<A>) -> (Constraint<A>,Vec<ParserErrorType>)  {
    loop {
        match ty {
            Type::TypeApp(_,lhs,rhs) => {
                acc.push( rhs.clone() );
                ty = lhs;
            },
            Type::TypeConstructor(_,name) => {
                let mut errs = vec![];
                for t in acc.iter() {
                  errs.extend(check_no_foralls(t));
                }
                
                return (Constraint::Constraint(_ann,name.clone(),acc),errs)
            },
            _ => {
                return (Constraint::Constraint(_ann,todo!(),vec![]),vec![ParserErrorType::ErrTypeInConstraint]);
            }
        }
    }
}

pub fn placeholder() -> SourceToken {
    let zero_pos = types::SourcePos::new(0, 0);
    SourceToken {
        ann:types::TokenAnn {range: types::SourceRange::new(zero_pos.clone(),zero_pos),leading_comments:vec![],trailing_comments:vec![] },
        value:types::Token::TokLowerName(String::from("<placeholder>"))
    }
}

pub fn separated<A>(mut arr:Vec<(SourceToken,A)>) -> types::Separated<A> {
    let mut accum:Vec<(SourceToken,A)> = vec![]; 
    let mut a:Option<A> = None;
   
    let mut idx = arr.len();
    while arr.len() > 0 {
       let elem = arr.pop().unwrap();
       if idx == 1 {
           a = Some(elem.1);
       } else {
            accum.push(elem);
       }
       idx -= 1;
    }
    types::Separated {
        head:a.unwrap(),
        tail:accum
    }
}



pub fn expr4_2(e1:types::Expr<()>,e2:types::Expr<()>) ->types::Expr<()> {
    match e2 {
        types::Expr::ExprApp(_,lhs,rhs) => {
            types::Expr::ExprApp((),Box::new(types::Expr::ExprApp((),Box::new(e1),lhs)),rhs)
        },
        _ => types::Expr::ExprApp((),Box::new(e1) ,Box::new(e2))
    }  
}

pub fn to_binder_constructor<A:Clone>(arr:Vec<types::Binder<A>>) -> Result<types::Binder<A>,ParserErrorType> {
    match arr.first() {
        Some(binder) => {
            if arr.len() == 1 {
                Ok(binder.clone())
            } else {
                match binder {
                    types::Binder::BinderConstructor(a,b,c) if c.len() == 0 => {
                        Ok(types::Binder::BinderConstructor(a.clone(),b.clone(),arr.clone().drain(1..).collect()))
                    },
                    _ => Err(ParserErrorType::ErrExprInBinder)
                }
            }
        },
        None => Err(ParserErrorType::ErrExprInBinder)
    }
}